﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using VS4CRM.Lib.Model;
using System.Xml;
using System.Threading;
using System.Xml.Linq;
using System.Web;
using Microsoft.Crm.SdkTypeProxy;
using Ionic.Zip;

namespace VS4CRM.Lib
{
    public class SynchronizationUtility
    {
        private const string XPATH_CUSOMIZABLE = "ImportExportXml/Entities/Entity[EntityInfo/entity/IsCustomizable=1]";
        private const string XPATH_WORKFLOWS = "ImportExportXml/Workflows/Workflow";
        private const string FORMAT_IMPORT_EPORT_PARAM = "<importexportxml><entities>{0}</entities><nodes>{1}</nodes><securityroles></securityroles><settings></settings><workflows>{2}</workflows></importexportxml>";

        public const string PATH_ENTITIES = "Entities";
        public const string PATH_EXTENTIONS = "Extentions";
        public const string PATH_WORKFLOWS = "Workflows";
        public const string PATH_ROLES = "Security Roles";
        public const string PATH_CONFIG = "Configuration";

        public event EventHandler<SynchronizationProgressEventArgs> ProgressChanged;

        private string _exportedCustomisations;
        private XElement _customizations;
        private IProjectHandler _projectHandler;
        List<AutoResetEvent> _events = new List<AutoResetEvent>();

        private SynchronizationProgressEventArgs _exportProgress;

        public SynchronizationUtility(CrmService service)
        {
            this.Service = service;
            this.Customization = new Services.Customizations(this.Service);
            InitializeCustomizationXml();
        }

        public CrmService Service { get; private set; }
        public Services.Customizations Customization { get; private set; }


        private void RaiseEvent(bool increment, string text)
        {
            lock (_exportProgress)
            {
                if (increment)
                    _exportProgress.Increment();
                _exportProgress.Text = text;

                ProgressChanged(this, _exportProgress);
            }
        }

        public bool RefreshProject(string projectFileName, bool skipIfExists)
        {
            _exportProgress = new SynchronizationProgressEventArgs();
            _exportProgress.TotalNumberOfSteps = 99;
            _exportProgress.Reset();

            RaiseEvent(false, "Loading Project");
            FileInfo projectFI = new FileInfo(projectFileName);
            XmlDocument projectDoc = new XmlDocument();
            projectDoc.Load(projectFI.ToString());
            _projectHandler = new XmlProjectHandler(projectDoc);

            RaiseEvent(false, "Getting customizations from CRM");
            LoadCustomizations();

            XmlDocument doc = new XmlDocument();
            doc.XmlResolver = null;
            doc.LoadXml(_exportedCustomisations);
            RaiseEvent(false, "Customizations loaded from CRM");

            //Creating customization files for entities
            DirectoryInfo entityDir = new DirectoryInfo(projectFI.Directory.ToString() + "\\" + PATH_ENTITIES);
            if (!entityDir.Exists)
            {
                entityDir.Create();
            }
            XmlNodeList entities = doc.SelectNodes(XPATH_CUSOMIZABLE);
            XmlNodeList workflows = doc.SelectNodes(XPATH_WORKFLOWS);
            _exportProgress.TotalStages = 3;
            _exportProgress.TotalNumberOfSteps = entities.Count + 4 + workflows.Count;
            RaiseEvent(false, "Begin Synchronizing Customizations.");
            foreach (XmlNode entity in entities)
            {

                EntityFile entityFile = new EntityFile()
                {
                    EntityXml = entity,
                    TargetPath = entityDir.ToString(),
                };
                if (entityFile.File.Exists && skipIfExists)
                {
                    RaiseEvent(true, String.Format("File {0} skipped.", entityFile.File.Name));
                    continue;
                }
                AutoResetEvent evnt = new AutoResetEvent(false);
                _events.Add(evnt);
                entityFile.Event = evnt;

                ThreadPool.QueueUserWorkItem(new WaitCallback(WriteEntityCustomizationToFile), entityFile);
                if (_events.Count > 3)
                {
                    WaitHandle.WaitAll(_events.ToArray());
                    _events.Clear();
                }
            }

            //Creating customization files for entities
            DirectoryInfo wfDir = new DirectoryInfo(projectFI.Directory.ToString() + "\\" + PATH_WORKFLOWS);
            if (!wfDir.Exists)
            {
                wfDir.Create();
            }
            RaiseEvent(false, "Begin Synchronizing Workflows.");
            if (_events.Count > 0)
            {
                AutoResetEvent[] waitEvents = _events.ToArray();
                WaitHandle.WaitAll(waitEvents);
                _events.Clear();
            }
            foreach (XmlNode workflow in workflows)
            {

                WorkflowFile wfFile = new WorkflowFile()
                {
                    WorkflowXml = workflow,
                    TargetPath = wfDir.ToString(),
                };
                if (wfFile.File.Exists && skipIfExists)
                {
                    RaiseEvent(true, String.Format("Workflow {0} skipped.", wfFile.File.Name));
                    continue;
                }
                AutoResetEvent evnt = new AutoResetEvent(false);
                _events.Add(evnt);
                wfFile.Event = evnt;

                ThreadPool.QueueUserWorkItem(new WaitCallback(WriteEntityCustomizationToFile), wfFile);
                if (_events.Count > 3)
                {
                    WaitHandle.WaitAll(_events.ToArray());
                    _events.Clear();
                }
            }


            DirectoryInfo extDir = new DirectoryInfo(projectFI.Directory.ToString() + "\\" + PATH_EXTENTIONS);
            if (!extDir.Exists)
            {
                extDir.Create();
            }

            WriteExtensionFile(skipIfExists, "ISV.Config", extDir, GetExtentionsCustomization("isvconfig"));

            WriteExtensionFile(skipIfExists, "sitemap", extDir, GetExtentionsCustomization("sitemap"));

            WriteExtensionFile(skipIfExists, "roles", extDir, GetSecurityRolesCustomization());

            WriteExtensionFile(skipIfExists, "settings", extDir, GetSettingsCustomization());

            if (_events.Count > 0)
            {
                AutoResetEvent[] waitEvents = _events.ToArray();
                WaitHandle.WaitAll(waitEvents);
                _events.Clear();
            }

            using (XmlTextWriter tw = new XmlTextWriter(projectFI.ToString(), Encoding.Default))
            {
                projectDoc.WriteTo(tw);
            }

            RaiseEvent(true, String.Format("Sinchronization of project {0} completed.", projectFI.Name));
            _exportProgress.Reset();
            _exportedCustomisations = string.Empty;
            return true;
        }


        #region ImportAllCustomization
        public bool CompileCustomization(string path, bool saveresult)
        {

            bool retValue = false;
            DirectoryInfo targetDir = new DirectoryInfo(path);
            if (!targetDir.Exists)
            {
                Console.Out.WriteLine("Path couln't be found");
                return false;
            }


            FileInfo[] files = targetDir.GetFiles("*.xml");
            foreach (FileInfo file in files)
            {
                retValue = retValue & AppendToCustomizations(file.FullName.ToString());
            }
            foreach (DirectoryInfo subdir in targetDir.GetDirectories())
            {
                retValue = retValue & CompileCustomization(subdir.FullName, false);
            }


            return retValue;
        }
        #endregion

        public byte[] SaveAsZip(string zipFilename, bool saveFile)
        {
            byte[] buffer = new byte[0];
            using (ZipFile zip = new ZipFile(zipFilename))
            {
                if (zip.Entries.Count > 0)
                {
                    zip.RemoveEntry("customizations.xml");
                }
                using (MemoryStream ms = new MemoryStream())
                {
                    XmlTextWriter xmlWritrer = new XmlTextWriter(ms, Encoding.Default);
                    _customizations.Save(xmlWritrer);
                    xmlWritrer.Flush();
                    zip.AddEntry("customizations.xml", "", ms.ToArray());
                    if (saveFile)
                        zip.Save();
                    using (MemoryStream msOut = new MemoryStream())
                    {
                        zip.Save(msOut);
                        buffer = msOut.ToArray();
                    }
                    xmlWritrer.Close();
                }

            }
            return buffer;
        }


        #region PushCustomizations
        public bool PushCustomizations(bool publishScript, bool saveresult)
        {
            DateTime start = DateTime.Now;
            byte[] customizations = SaveAsZip("test.zip", saveresult);
            this.Customization.ImportAllZip(customizations);
             Console.WriteLine("Zip = {0}", DateTime.Now - start);
             start = DateTime.Now;
            this.Customization.ImportAllXml(_customizations.ToString());
            Console.WriteLine("ImportAll = {0}", DateTime.Now - start);
            return true;

            bool result = false;
            try
            {
                XElement importRequest = new XElement("importexportxml",
                    new XElement("entities"),
                    new XElement("nodes"),
                    new XElement("securityroles"),
                    new XElement("settings"),
                    new XElement("workflows"));

                IEnumerable<String> entityNames = from e in _customizations.Element("Entities").
                Elements("Entity").Elements("Name")
                                                  where !String.IsNullOrEmpty(e.Value)
                                                  select e.Value;



                foreach (string name in entityNames)
                {
                    importRequest.Element("entities").Add(new XElement("entity", name));
                }

                if (_customizations.Descendants("IsvConfig").Count() > 0)
                {
                    importRequest.Element("nodes").Add(new XElement("node", "isvconfig"));
                }


                if (_customizations.Descendants("SiteMap").Count() > 0)
                {
                    importRequest.Element("nodes").Add(new XElement("node", "sitemap"));
                }


                result = this.Customization.Import(importRequest.ToString(), _customizations.ToString());
                //if (publishScript)
                 //    result = PublishCustomization(importRequest.ToString());
            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                InitializeCustomizationXml();
            }
            return result;

        }
        #endregion

        private void InitializeCustomizationXml()
        {
            _customizations = new XElement("ImportExportXml",
          new XAttribute("version", "4.0.0.0"),
          new XAttribute("languagecode", "1033"),
          new XAttribute("generatedBy", "VS"),

          new XElement("Entities"),
          new XElement("Roles"),
          new XElement("Workflows"),

          new XElement("EntityMaps"),
          new XElement("EntityRelationships"),
          new XElement("Languages",
          new XElement("Language", "1033")));
        }

        #region AppendToCustomizations
        public bool AppendToCustomizations(string fileName)
        {
            bool result = false;

            FileInfo fi = new FileInfo(fileName);


            if (fi.Exists)
            {
                XDocument doc = XDocument.Load(fi.FullName);

                foreach (XElement node in doc.Root.Elements().Where(d => d.Name != "Languages"))
                {
                    IEnumerable<XElement> Elements = _customizations.Elements(node.Name);
                    if (Elements.Count() == 1)
                    {
                        _customizations.Elements(node.Name).First().Add(node.Elements());
                    }
                }

                if (doc.Root.Elements("Templates").Count() != 0)
                {
                    _customizations.Element("Workflows").AddAfterSelf(doc.Root.Elements("Templates"));
                }

                if (doc.Root.Elements("IsvConfig").Count() != 0)
                {
                    if (_customizations.Element("Templates") != null)
                        _customizations.Element("Templates").AddAfterSelf(doc.Root.Elements("IsvConfig"));
                    else if (_customizations.Element("SiteMap") != null)
                        _customizations.Element("SiteMap").AddBeforeSelf(doc.Root.Elements("IsvConfig"));
                    else
                        _customizations.Element("EntityMaps").AddBeforeSelf(doc.Root.Elements("IsvConfig"));

                }

                if (doc.Root.Elements("SiteMap").Count() != 0)
                {
                    _customizations.Element("EntityMaps").AddBeforeSelf(doc.Root.Elements("SiteMap"));
                }
            }

            return result;
        }
        #endregion

        private void WriteExtensionFile(bool skipIfExists, string extensionType, DirectoryInfo parentDir, string customization)
        {
            FileInfo file = new FileInfo(String.Format(@"{0}\{1}.xml", parentDir.ToString(), extensionType));
            if (!file.Exists || !skipIfExists)
            {
                FileStream fsSitemap = new FileStream(file.ToString(), FileMode.OpenOrCreate);
                StreamWriter fsSitemapWriter = new StreamWriter(fsSitemap);
                fsSitemapWriter.Write(customization);
                fsSitemapWriter.Close();
                fsSitemap.Close();

                _projectHandler.AddFileToProject(PATH_EXTENTIONS, file.Name);

                RaiseEvent(true, String.Format("File {0} synchonized.", file.Name));
            }
            else
            {
                RaiseEvent(true, String.Format("File {0} skipped.", file.Name));
            }
        }

        #region LoadCustomizations
        public void LoadCustomizations()
        {
            _exportedCustomisations = Customization.ExportAll();
        }
        #endregion

        #region GetEntityCustomization
        public string GetEntityCustomization(string name)
        {
            XElement customisations = XElement.Parse(_exportedCustomisations);
            customisations.Element("Entities").Elements("Entity").Where(en => en.Element("Name").Value != name).Remove();
            customisations.Element("Roles").Elements().Remove();
            customisations.Element("Workflows").Elements().Remove();
            customisations.Element("EntityMaps").Elements("EntityMap").Where(en => en.Element("EntitySource").Value.ToLower() != name.ToLower()
            && en.Element("EntityTarget").Value.ToLower() != name.ToLower()).Remove();

            customisations.Element("EntityRelationships").Elements("EntityRelationship").
                Where(en => (en.Element("EntityRelationshipType").Value != "ManyToMany" && en.Element("ReferencingEntityName").Value.ToLower() != name.ToLower()
                            && en.Element("ReferencedEntityName").Value.ToLower() != name.ToLower())
                            || (en.Element("EntityRelationshipType").Value == "ManyToMany" && en.Element("FirstEntityName").Value.ToLower() != name.ToLower()
                            && en.Element("SecondEntityName").Value.ToLower() != name.ToLower())).Remove();

            customisations.Element("Templates").Remove();
            customisations.Element("IsvConfig").Remove();
            customisations.Element("SiteMap").Remove();
            customisations.Element("OrganizationSettings").Remove();

            return customisations.ToString();
        }
        #endregion

        #region GetRolesCustomization
        public string GetSecurityRolesCustomization()
        {
            XElement customisations = XElement.Parse(_exportedCustomisations);
            customisations.Element("Entities").Elements().Remove();
            customisations.Element("Workflows").Elements().Remove();
            customisations.Element("EntityMaps").Elements().Remove();
            customisations.Element("EntityRelationships").Elements().Remove();

            customisations.Element("RelationshipRoles").Remove();
            customisations.Element("Templates").Remove();
            customisations.Element("IsvConfig").Remove();
            customisations.Element("SiteMap").Remove();
            customisations.Element("OrganizationSettings").Remove();

            return customisations.ToString();
        }
        #endregion

        #region GetSettingsCustomization
        public string GetSettingsCustomization()
        {
            XElement customisations = XElement.Parse(_exportedCustomisations);
            customisations.Element("Entities").Elements().Remove();
            customisations.Element("Roles").Elements().Remove();
            customisations.Element("Workflows").Elements().Remove();
            customisations.Element("EntityMaps").Elements().Remove();
            customisations.Element("EntityRelationships").Elements().Remove();

            customisations.Element("IsvConfig").Remove();
            customisations.Element("SiteMap").Remove();

            return customisations.ToString();
        }
        #endregion

        #region GetWorkflowsCustomization
        public string GetWorkflowCustomization(Guid workflowid)
        {
            XElement customisations = XElement.Parse(_exportedCustomisations);
            if (customisations.Element("Workflows").Elements("Workflow").Where(en => new Guid(en.Attribute("workflowid").Value) == workflowid).Count() > 0)
            {
                customisations.Element("Entities").Elements().Remove();
                customisations.Element("Workflows").Elements("Workflow").Where(en => new Guid(en.Attribute("workflowid").Value) != workflowid).Remove();
                customisations.Element("Roles").Elements().Remove();
                customisations.Element("EntityMaps").Elements().Remove();

                customisations.Element("EntityRelationships").Elements().Remove();

                customisations.Element("RelationshipRoles").Remove();
                customisations.Element("Templates").Remove();
                customisations.Element("IsvConfig").Remove();
                customisations.Element("SiteMap").Remove();
                customisations.Element("OrganizationSettings").Remove();

                return customisations.ToString();
            }
            else
            {
                string xmlRequestParam = string.Format(FORMAT_IMPORT_EPORT_PARAM,
                String.Empty,
                String.Empty,
                String.Format("<workflow>{0}</workflow>", workflowid));
                return Customization.Export(xmlRequestParam);
            }
        }
        #endregion

        #region CreateScriptFile
        private void CreateScriptFile(string eventName, XmlDocument customizationXML, string fileName, string filePath)
        {
            using (FileStream fs = new FileStream(filePath + "\\" + fileName, FileMode.Create))
            {
                string script = "";
                XmlNode scriptNode = customizationXML.SelectSingleNode(String.Format("//forms/entity/form[@type='main']/events/event[@name=\"{0}\"]/script", eventName));
                if (scriptNode != null)
                    script = HttpUtility.HtmlDecode(scriptNode.InnerText);

                StreamWriter writer = new StreamWriter(fs);
                writer.Write(script);
                writer.Close();
            }
        }
        #endregion

        #region WriteEntityCustomizationToFile
        private void WriteEntityCustomizationToFile(object customizationInfo)
        {

            if (customizationInfo is EntityFile)
            {
                EntityFile entity = customizationInfo as EntityFile;
                try
                {
                    string entityName = entity.EntityName;
                    string customization = GetEntityCustomization(entityName);

                    using (FileStream fs = new FileStream(String.Format(@"{0}\{1}.xml", entity.TargetPath, entityName), FileMode.Create))
                    {
                        StreamWriter writer = new StreamWriter(fs);
                        writer.Write(customization);
                        writer.Close();
                    }

                    XmlDocument customizationXML = new XmlDocument();
                    customizationXML.LoadXml(customization);
                    CreateScriptFile("onload", customizationXML, String.Format(@"{0}.xml.onload.js", entityName), entity.TargetPath);
                    CreateScriptFile("onsave", customizationXML, String.Format(@"{0}.xml.onsave.js", entityName), entity.TargetPath);

                    string[] depenants = new string[] { String.Format(@"{0}.xml.onload.js", entityName), String.Format(@"{0}.xml.onsave.js", entityName) };

                    _projectHandler.AddFileToProject(PATH_ENTITIES, String.Format("{0}.xml", entityName), depenants);
                }
                catch (Exception ex)
                {
                    string s = ex.Message;
                }
                finally
                {
                    if (entity != null && entity.Event != null)
                    {
                        RaiseEvent(true, String.Format("File {0} synchronized.", entity.File.Name));
                        entity.Event.Set();
                    }
                }
            }
            else if (customizationInfo is WorkflowFile)
            {
                WorkflowFile wfFile = customizationInfo as WorkflowFile;
                try
                {
                    string customization = GetWorkflowCustomization(wfFile.ID);

                    using (FileStream fs = new FileStream(String.Format(@"{0}\{1}.xml", wfFile.TargetPath, wfFile.Name), FileMode.Create))
                    {
                        StreamWriter writer = new StreamWriter(fs);
                        writer.Write(customization);
                        writer.Close();
                    }

                    _projectHandler.AddFileToProject(PATH_WORKFLOWS, String.Format("{0}.xml", wfFile.Name));
                }
                catch (Exception ex)
                {
                    string s = ex.Message;
                }
                finally
                {
                    if (wfFile != null && wfFile.Event != null)
                    {
                        RaiseEvent(true, String.Format("Workflow {0} synchronized.", wfFile.Name));
                        wfFile.Event.Set();
                    }
                }
            }
        }
        #endregion

        #region GetExtentionsCustomization
        public string GetExtentionsCustomization(string configGUID)
        {
            string xmlRequestParam = string.Format(FORMAT_IMPORT_EPORT_PARAM,
                String.Empty,
                String.Format("<node>{0}</node>", configGUID),
                String.Empty);
            return Customization.Export(xmlRequestParam);
        }
        #endregion


    }
}
